import express, { Express, Request, Response, NextFunction } from "express";
import { Logger } from "./logger";
import "reflect-metadata";
import path from "path";
import { INJECTEDTOKENS } from "@nestjs/constant";
import { defineModule, RequestMethod } from "@nestjs/common";
export class NestApplication {
  private readonly app: Express = express();
  private request = null;
  // 此处保存全部的provider
  private providers = new Map();
  private providerInstance = new Map();
  private moduleProviders = new Map();
  private globalProviders = new Set();
  private readonly middlewares = [];
  constructor(protected readonly module: any) {
    // 配置body请求体中间件
    this.app.use(express.json());
    this.app.use(express.urlencoded({ extended: true }));
    this.app.use((req, res, next) => {
      req.user = { name: "admin" };
      this.request = req;
      next();
    });
  }
  // 中间件
  use(middleware: any) {
    this.app.use(middleware);
  }

  switchToHttp() {
    return {
      getRequest: () => {
        return this.request;
      },
    };
  }

  private async initMiddlewares() {
    this.module.prototype.configure?.(this);
  }

  apply(...middlewares) {
    defineModule(this.module, middlewares);
    this.middlewares.push(...middlewares);
    return this;
  }

  forRoutes(...routes: Array<string | { path: string; method: RequestMethod } | Function>) {
    for (const route of routes) {
      for (const middleware of this.middlewares) {
        const { routePath, routeMethod } = this.normalizeRouteInfo(route);
        // 匹配路径
        this.app.use(routePath, (req, res, next) => {
          // 匹配方法
          if (routeMethod === RequestMethod.ALL || routeMethod === req.method) {
            const middlewareInstance = this.getMiddlewareInstance(middleware);
            if (middlewareInstance) {
              middlewareInstance.use(req, res, next);
            } else {
              next();
            }
          }
        });
      }
    }
  }

  getMiddlewareInstance(middleware) {
    if (typeof middleware === "function") {
      const dependencies = this.resolveDependencies(middleware);
      console.log("getMiddlewareInstance", dependencies);
      return new middleware(...dependencies);
    }
    if (typeof middleware === "object") {
      return middleware;
    }
    return null;
  }

  normalizeRouteInfo(routes: string | { path: string; method: RequestMethod } | Function): {
    routePath: string;
    routeMethod: RequestMethod;
  } {
    const newRoutes = {
      routePath: "",
      routeMethod: RequestMethod.ALL,
    };
    if (typeof routes === "string") {
      newRoutes.routePath = routes;
      newRoutes.routeMethod = RequestMethod.ALL;
    } else if ("path" in routes) {
      newRoutes.routePath = routes.path;
      newRoutes.routeMethod = RequestMethod.ALL;
    }
    newRoutes.routePath = path.posix.join("/", newRoutes.routePath);
    return newRoutes;
  }

  private async initProviders() {
    const imports = Reflect.getMetadata("imports", this.module) ?? [];
    for (let importModule of imports) {
      if (importModule instanceof Promise) {
        importModule = await importModule;
      }
      // 处理动态模块
      if (importModule.module) {
        const { module: myModule, providers, exports, controllers } = importModule;
        const oldControllers = Reflect.getMetadata("controllers", myModule) ?? [];
        const oldProviders = Reflect.getMetadata("providers", myModule) ?? [];
        const oldExports = Reflect.getMetadata("exports", myModule) ?? [];
        const newControllers = [...(controllers ?? []), ...(oldControllers ?? [])];
        const newProviders = [...(providers ?? []), ...(oldProviders ?? [])];
        const newExports = [...(exports ?? []), ...(oldExports ?? [])];
        // console.log(newProviders, newExports);
        defineModule(myModule, newControllers);
        defineModule(myModule, newProviders);
        Reflect.defineMetadata("controllers", newControllers, myModule);
        Reflect.defineMetadata("providers", newProviders, myModule);
        Reflect.defineMetadata("exports", newExports, myModule);

        this.registerProviderFromModule(myModule, this.module);
      } else {
        this.registerProviderFromModule(importModule, this.module);
      }
    }
    // 处理当前模块
    const providers = Reflect.getMetadata("providers", this.module) ?? [];
    for (const provider of providers) {
      this.addProvider(provider, this.module);
    }
  }

  /**
   * @description 将所依赖模块导出的provider递归注册
   */
  private registerProviderFromModule(module, ...parentModules) {
    const isGlobal = Reflect.getMetadata("isGlobal", module);
    // 处理引入的子模块 这里得用exports,因为可能只导出部分服务
    const providers = Reflect.getMetadata("providers", module) ?? [];
    // console.log("registerProviderFromModule providers", providers);
    const exports = Reflect.getMetadata("exports", module) ?? [];
    for (const exportToken of exports) {
      if (this.isModule(exportToken)) {
        this.registerProviderFromModule(exportToken, module, ...parentModules);
      } else {
        const provider = providers.find((provider) => provider === exportToken || provider.provide === exportToken);
        // provider && this.addProvider(provider, module);
        if (provider) {
          // 将此provider注册到自己的父及其以上的模块
          [module, ...parentModules].forEach((m) => this.addProvider(provider, m, isGlobal));
        }
      }
    }
  }

  private addProvider(provider, module, isGlobal = false) {
    const providers = isGlobal ? this.globalProviders : this.moduleProviders.get(module) || new Set();
    if (!this.moduleProviders.has(module)) {
      this.moduleProviders.set(module, providers);
    }
    const injectToken = provider.provide ?? provider;
    //如果实例池里已经有此token对应的实例了
    if (this.providerInstance.has(injectToken)) {
      //则直接把此token放入到providers这个集合直接返回
      if (!providers.has(injectToken)) {
        providers.add(injectToken);
      }
      return;
    }
    // const providerKey = provider.provide ?? provider;
    // // 避免重复注册
    // if (this.providers.has(providerKey)) {
    //   return;
    // }
    if (provider.provide && provider.useClass) {
      const dependencies = this.resolveDependencies(provider.useClass);
      const instance = new provider.useClass(...dependencies);
      // this.providers.set(provider.provide, instance);
      this.providerInstance.set(provider.provide, instance);
      providers.add(provider);
    } else if (provider.provide && provider.useFactory) {
      const inject = provider.inject ?? [];
      const injectValue = inject.map((inject) => this.getProviderByToken(inject, module));
      this.providerInstance.set(provider.provide, provider.useFactory(...injectValue));
      providers.add(provider);
    } else if (provider.provide && provider.useValue) {
      console.log("provider.useValue", provider.useValue);
      this.providerInstance.set(provider.provide, provider.useValue);
      providers.add(provider);
    } else {
      // console.log("provider", provider);
      const dependencies = this.resolveDependencies(provider);
      // console.log("dependencies", dependencies);
      this.providerInstance.set(provider, new provider(...dependencies));
      // console.log("this.providerInstance", this.providerInstance);
      providers.add(provider);
    }

    // this.moduleProviders.set(module, providers);
  }

  private getProviderByToken(token, module) {
    if (this.moduleProviders.get(module)?.has(token) || this.globalProviders.has(token)) {
      return this.providerInstance.get(token);
    } else {
      return null;
    }
  }

  private isModule(provider) {
    return provider && provider instanceof Function && Reflect.getMetadata("isModule", provider);
  }

  private resolveDependencies(Controller: Function) {
    const injectTokens = Reflect.getMetadata(INJECTEDTOKENS, Controller) ?? [];
    const constructorParams = Reflect.getMetadata("design:paramtypes", Controller) ?? [];

    return constructorParams.map((param, index) => {
      const module = Reflect.getMetadata("nestModule", Controller);
      // console.log("constructorParams", module, Controller.name);
      return this.getProviderByToken(injectTokens[index] ?? param, module);
    });
  }

  // 解析路由
  async init() {
    // console.log("init");
    const controllers = Reflect.getMetadata("controllers", this.module) ?? [];
    Logger.log("AppModule dependencies initialized", "InstanceLoader");
    for (const Controller of controllers) {
      // 解析出控制器的依赖
      const dependencies = this.resolveDependencies(Controller);
      // console.log("init", dependencies);
      // 使用provider的实例
      const controller = new Controller(...dependencies);
      const prefix = Reflect.getMetadata("prefix", Controller) ?? "/";
      Logger.log(`${Controller.name} {${prefix}}`, "RouterResolver");
      const controllerPrototype = Controller.prototype;
      for (const methodName of Object.getOwnPropertyNames(controllerPrototype)) {
        // 获取实例方法
        const method = controllerPrototype[methodName];
        const pathName = Reflect.getMetadata("path", method) ?? "";
        const methodType = Reflect.getMetadata("method", method) ?? "";
        if (!methodType) {
          continue;
        }
        // express 使用路由 posix处理 适配/\
        const routePath = path.posix.join("/", prefix, pathName);
        // console.log(routePath, methodType, methodName, method);
        this.app[methodType.toLowerCase()](routePath, async (req: Request, res: Response, next: NextFunction) => {
          const args = this.resolveParmas(controller, methodName, req, res, next);
          // 绑定实例的this到对应的路由方法内
          const result = await method.call(controller, ...args);
          const isNeedHandSendRes = this.handleIsNeedHandSendRes(controller, methodName);
          // console.log("isNeedHandSendRes", isNeedHandSendRes);
          if (!isNeedHandSendRes) {
            res.json(result);
          }
        });

        Logger.log(`Mapped {${routePath}, ${methodType}} route`, "RouterResolver");
      }
    }

    Logger.log(`Nest Application successfully started`, "NestApplication");
  }

  private handleIsNeedHandSendRes(controller: any, methodName: string) {
    const paramMetadata = Reflect.getMetadata(`param`, controller, methodName) ?? [];
    // console.log("handleIsNeedHandSendRes", paramMetadata);
    // 存在res 且 passthrough 为false或者undefined
    const resMetadata = paramMetadata
      .filter(Boolean)
      .find((param) => param.key === "Res" ?? param.key === "Response" ?? param.key === "Next");
    // console.log(resMetadata, "sss");
    // 1. resMetadata不存在  2. resMetadata存在但他的data存在 passthrough 为true => 不需要手动返回响应
    if (!resMetadata ?? (resMetadata?.data && resMetadata.data?.passthrough)) {
      return false;
    }
    return true;
  }

  private resolveParmas(instance: any, methodName: string, req: Request, res: Response, next: NextFunction): any[] {
    const paramMetadata = Reflect.getMetadata(`param`, instance, methodName) ?? [];
    // console.log(paramMetadata);
    return (
      paramMetadata
        // .sort((a, b) => a.parameterIndex - b.parameterIndex)
        .map((param) => {
          switch (param.key) {
            case "Request":
            case "Req":
              return req;
            case "Query":
              return param.data ? req.query[param.data] : req.query;
            case "Headers":
              return param.data ? req.headers[param.data] : req.headers;
            case "Body":
              return param.data ? req.body[param.data] : req.body;
            case "Response":
            case "Res":
              return res;
            case "Next":
              return next;
            case "DecoratorFatcory":
              return param.factory.apply(this, [param.data, this]);
            default:
              return null;
          }
        })
    );
  }

  async listen(port: number) {
    // 初始化provider
    await this.initProviders();
    await this.initMiddlewares();
    await this.init();
    this.app.listen(port, () => {
      Logger.log(`Application is running at http://localhost:${port}`, "NestApplication");
    });
  }
}
